package demo.javax.crypto;

import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 加密器/解密器
 * 
 * Cipher类为加密和解密提供密码功能。它构成了Java Cryptographic Extension（JCE）框架的核心。
 * 
 * Cipher类是一个引擎类，它需要通过getInstance()工厂方法来实例化对象。我们可以通过指定转换模式的方式获得实例化对象。
 * 
 * @author hanjy
 *
 */
public class CipherDemo {

    private static Logger logger = LoggerFactory.getLogger(CipherDemo.class);

    /**
     * 上述实例化操作是一种最为简单的实现，并没有考虑DES分组算法的工作模式和填充模式，可通过以下方式对其设定。
     * 
     * 参数transformation的格式是“算法/工作模式/填充模式”，不同的算法支持不同的工作模式以及填充模式。
     */
    @Test
    public void testDES() {
        try {
            Cipher c1 = getInstance(Constants.Transformation.DES.getName());
            Cipher c3 = getInstance(Constants.Transformation.DES_CBC_PKCS5PADDING.getName());
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            logger.error("", e);
        }
    }

    @Test
    public void test2() {
        String data = "hello Stan.";
        byte[] KEY_DATA = { 1, 8, -49, -31, 77, 90, 10, 121, -14, 109, 107, 38, 29, 68, 59, 5, 82, 49, 31, 42, -25, 67,
                96, 15 };
        try {
            Cipher cipher = getInstance(Constants.Transformation.DESEDE_ECB_PKCS5PADDING.getName());
            // 生成加密解密需要的Key
            SecretKeySpec key = new SecretKeySpec(KEY_DATA, Constants.Algorithm.DESede.getName());
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] res = cipher.doFinal(data.getBytes());
            logger.info("原始数据:{}", data);
            logger.info("加密后数据：{}", Base64.getEncoder().encodeToString(res));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** 用于将Cipher初始化为解密模式的常量 */
    public final static int DECRYPT_MODE = Cipher.DECRYPT_MODE;

    /** 用于将Cipher初始化为加密模式的常量 */
    public final static int ENCRYPT_MODE = Cipher.ENCRYPT_MODE;

    /**
     * 通过指定转换模式的方式获得实例化对象
     * 
     * @param transformation
     *            格式是“算法/工作模式/填充模式”，不同的算法支持不同的工作模式以及填充模式。 或“算法”（后一种情况下，使用模式和填充方案特定于提供者的默认值）
     * 
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     */
    public static Cipher getInstance(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException {
        return Cipher.getInstance(transformation);
    }

    /**
     * 在指定转换模式的同时指定该转换模式的提供者
     * 
     * @param transformation
     *            格式是“算法/工作模式/填充模式”，不同的算法支持不同的工作模式以及填充模式。
     * @param provider
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     */
    public static Cipher getInstance(String transformation, Provider provider)
            throws NoSuchAlgorithmException, NoSuchPaddingException {
        return Cipher.getInstance(transformation, provider);
    }

    /**
     * 用指定的密钥和模式初始化 Cipher 对象
     * 
     * @param cipher
     * @param opmode
     *            方式 (ENCRYPT_MODE, DECRYPT_MODE, WRAP_MODE,UNWRAP_MODE)
     * @param key
     * @throws InvalidKeyException
     */
    public static void init(Cipher cipher, int opmode, Key key) throws InvalidKeyException {
        cipher.init(opmode, key);
    }

    /**
     * 用密钥和一组算法参数初始化此Cipher对象
     * 
     * @param cipher
     * @param opmode
     * @param key
     * @param params
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     */
    public static void init(Cipher cipher, int opmode, Key key, AlgorithmParameters params)
            throws InvalidKeyException, InvalidAlgorithmParameterException {
        cipher.init(opmode, key, params);
    }

    /**
     * 用一个密钥、一组算法参数和一个随机源初始化此Cipher对象
     * 
     * @param cipher
     * @param opmode
     * @param key
     * @param params
     * @param random
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     */
    public static void init(Cipher cipher, int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random)
            throws InvalidKeyException, InvalidAlgorithmParameterException {
        cipher.init(opmode, key, params, random);
    }

    /**
     * 用密钥和随机源初始化此Cipher对象
     * 
     * @param cipher
     * @param opmode
     * @param key
     * @param random
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     */
    public static void init(Cipher cipher, int opmode, Key key, SecureRandom random)
            throws InvalidKeyException, InvalidAlgorithmParameterException {
        cipher.init(opmode, key, random);
    }

    /**
     * 通过以下方法可借助于证书，获取其公钥来完成加密和解密操作：
     * 
     * 用取自给定证书的公钥初始化此Cipher对象
     * 
     * @param cipher
     * @param opmode
     * @param certificate
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     */
    public static void init(Cipher cipher, int opmode, Certificate certificate)
            throws InvalidKeyException, InvalidAlgorithmParameterException {
        cipher.init(opmode, certificate);
    }

    /**
     * 用取自给定证书的公钥和随机源初始化此Cipher对象
     * 
     * @param cipher
     * @param opmode
     * @param certificate
     * @param random
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     */
    public static void init(Cipher cipher, int opmode, Certificate certificate, SecureRandom random)
            throws InvalidKeyException, InvalidAlgorithmParameterException {
        cipher.init(opmode, certificate, random);
    }

    /**
     * 如果需要多次更新待加密（解密）的数据可使用如下方法。
     * 
     * 最为常用的是通过输入给定的字节数组完成更新：
     * 
     * 继续多部分加密或解密操作（具体取决于此Cipher对象的初始化方式），以处理其他数据部分
     * 
     * @param cipher
     * @param input
     * @return
     */
    public static byte[] update(Cipher cipher, byte[] input) {
        return cipher.update(input);
    }

    /** 通过偏移量的方式完成更新，继续多部分加密或解密操作（具体取决于此Cipher对象的初始化方式），以处理其他数据部分 */
    public static byte[] update(Cipher cipher, byte[] input, int inputOffset, int inputLen)
            throws ShortBufferException {
        return cipher.update(input, inputOffset, inputLen);
    }

    /** 另外一种方式就是将更新结果输出至参数中。继续多部分加密或解密操作（具体取决于此Cipher对象的初始化方式），以处理其他数据部分 */
    public static void update(Cipher cipher, byte[] input, int inputOffset, int inputLen, byte[] output)
            throws ShortBufferException {
        cipher.update(input, inputOffset, inputLen, output);
    }

    /** 继续多部分加密或解密操作（具体取决于此Cipher对象的初始化方式），以处理其他数据部分 */
    public static void update(Cipher cipher, byte[] input, int inputOffset, int inputLen, byte[] output,
            int outputOffset) throws ShortBufferException {
        cipher.update(input, inputOffset, inputLen, output, outputOffset);
    }

    /** 使用如下缓冲方式.继续多部分加密或解密操作（具体取决于此Cipher对象的初始化方式），以处理其他数据部分 */
    public static void update(Cipher cipher, ByteBuffer input, ByteBuffer output) throws ShortBufferException {
        cipher.update(input, output);
    }

    /** 完成上述数据更新后，直接执行如下方法：结束多部分加密或解密操作（具体取决于此Cipher对象的初始化方式） */
    public static byte[] doFinal(Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
        return cipher.doFinal();
    }

    /**
     * 对 input 内的串 , 进行编码处理 , 返回处理后二进制串 , 是返回解密文还是加解文由 init 时的 opmode 决定。
     * 
     * 本方法的执行前如果有 update, 是对 update 和本次 input 全部处理 , 否则是本 input 的内容
     * 
     * @param cipher
     * @param input
     * @return
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public static byte[] dofinal(Cipher cipher, byte[] input) throws IllegalBlockSizeException, BadPaddingException {
        return cipher.doFinal(input);
    }

}
